#include "KnapsackEnvironment.h"
#include "../../Exception/GA_Exception.h"

using namespace std;

template <typename T>
Environment* KnapsackEnvironment<T>::getEnvironment()
{
    //TODO: if we provide a non-commandline interface to getEnvironment,
    //this function will need to be reworked to use that interface.
    
    cout << "Which of the following solution encodings do you wish to use?" << endl;
    cout << BIT_ENCODED_SOLUTION << ") Bit Encoded Solutions" << endl;
    cout << PERMUTATION_ENCODED_SOLUTION << ") Permutation Encoded Solutions" << endl;
    int answer;
            
    while (true) 
    {
        cout << "? ";
        cin >> answer;
        
        if (answer == BIT_ENCODED_SOLUTION) {
            return new KnapsackEnvironment<BitEncodedSolution>();
        }
        else if (answer == PERMUTATION_ENCODED_SOLUTION) {
            return new KnapsackEnvironment<PermutationEncodedSolution>();
        }
    }
}

template <typename T>
KnapsackEnvironment<T>::KnapsackEnvironment() 
{
    throw Unsupported_template_exception();
}

KnapsackEnvironment<PermutationEncodedSolution>::KnapsackEnvironment() 
{
    init();
}

KnapsackEnvironment<BitEncodedSolution>::KnapsackEnvironment()
{
    init();
}

template <typename T>
void KnapsackEnvironment<T>::configure() 
{
    cout << "Would you like to generate items:" << endl;
    cout << "1) Randomly" << endl;
    cout << "2) From a file" << endl;
    int answer;
    
    cin >> answer;
    
    if (answer == 1) {
        populateItemsRandomly();
    }
    else {
        string filename;
        cout << "Enter the filename to read items from:" << endl;
        cin >> filename;
        populateItemsFromFile(filename);
    }
    
    cout << "What would you like as the maximum allowable weight?" << endl;
    cin >> maxAllowableWeight;
    
    maxAllowableWeight = max(maxAllowableWeight, 0.0);
    
    configured = true;
}

template <typename T>
void KnapsackEnvironment<T>::configure(string filename)
{
}

template <typename T>
void KnapsackEnvironment<T>::init()
{
    totalItemsWeight = 0;
    configured = false;
}

template <typename T>
KnapsackEnvironment<T>::~KnapsackEnvironment() 
{
}

template <typename T>
string KnapsackEnvironment<T>::formatSolution(Solution* solution) 
{
    return Environment::formatSolution(solution);
}

Crossover* KnapsackEnvironment<BitEncodedSolution>::configureCrossover(KnapsackEnvironmentCrossoverType type)
{
    if (type == SINGLE_POINT_CROSSOVER) {
        return new SinglePointCrossover<BitEncodedSolution>();
    }
    else if (type == TWO_POINT_CROSSOVER) {
        return new TwoPointCrossover<BitEncodedSolution>();
    }
    else if (type == UNIFORM_CROSSOVER) {
        return new UniformCrossover<BitEncodedSolution>();
    }
    else {
        throw Unsupported_crossover_exception();
    }
}

Crossover* KnapsackEnvironment<BitEncodedSolution>::configureCrossover() 
{
    cout << "What type of crossover do you want to use?" << endl;
    cout << SINGLE_POINT_CROSSOVER << ") Single Point Crossover" << endl;
    cout << TWO_POINT_CROSSOVER << ") Two Point Crossover" << endl;
    cout << UNIFORM_CROSSOVER << ") Uniform Crossover" << endl;
    cout << '?';
    int answer;
    
    while (true) {
        cin >> answer;
        
        if (answer == SINGLE_POINT_CROSSOVER) {
            return configureCrossover(SINGLE_POINT_CROSSOVER);
        }
        else if (answer == TWO_POINT_CROSSOVER) {
            return configureCrossover(TWO_POINT_CROSSOVER);
        }
        else if (answer == UNIFORM_CROSSOVER) {
            return configureCrossover(UNIFORM_CROSSOVER);
        }
        else {
            cout << "Invalid selection.  Please try again." << endl;
            cout << '?';
        }
    }
}

double KnapsackEnvironment<BitEncodedSolution>::calcSolutionValue(BitEncodedSolution* solution) 
{
    double returnMe = 0.0;
    
    for (unsigned int x = 0; x < solution->getSize(); x++) 
    {
        if (solution->getBit(x)) 
        {
            returnMe += items[x].value;
        }
    }
    
    return returnMe;
}

double KnapsackEnvironment<BitEncodedSolution>::calcSolutionWeight(BitEncodedSolution* solution) 
{
    double returnMe = 0.0;
    
    for (unsigned int x = 0; x < solution->getSize(); x++) 
    {
        if (solution->getBit(x)) 
        {
            returnMe += items[x].weight;
        }
    }
    
    return returnMe;
}

string KnapsackEnvironment<BitEncodedSolution>::formatSolution(Solution* solution) 
{
    BitEncodedSolution* besSolution = (BitEncodedSolution*) solution;
    ostringstream returnMe;
    
    returnMe << "Items: ";
    
    double solutionWeight = calcSolutionWeight(besSolution);
    double solutionValue = calcSolutionValue(besSolution);
    string separator = "";
    
    for (unsigned int x = 0; x < besSolution->getSize(); x++) 
    {
        if (besSolution->getBit(x)) 
        {
            returnMe << separator << (x + 1);
            separator = ", ";
        }
    }
    
    returnMe << " (weight: " << solutionWeight << ", value: " << solutionValue << ")";
                 
    return returnMe.str();
}

double KnapsackEnvironment<BitEncodedSolution>::evaluateFitness(Solution* solution) 
{
    BitEncodedSolution* besSolution = (BitEncodedSolution*) solution;
    
    double solutionWeight = calcSolutionWeight(besSolution);
    double solutionValue = calcSolutionValue(besSolution);
    double baseValue = 100;
    
    //if a solution is under the weight limit, it gets to bring it's items
    if (solutionWeight <= maxAllowableWeight) {
        solutionValue += baseValue;
    }
    //otherwise, it only gets it's base value. 
    else {
        solutionValue = (baseValue - ((solutionWeight/totalItemsWeight) * baseValue));
    }
    
    return solutionValue;
}

void KnapsackEnvironment<BitEncodedSolution>::generateRandomPopulation(vector<Solution*> &solutions, unsigned int size) 
{
    int itemCount = items.size();
    
    for (unsigned int x = 0; x < size; ++x) 
    {
        BitEncodedSolution* solution = new BitEncodedSolution();
        for (int y = 0; y < itemCount; ++y) 
        {
            solution->addBit(false);
        }
        
        double solutionWeight = calcSolutionWeight(solution);        
        
        //TODO: is this a good idea?  I'm really just flailing about until I
        //accidently generate a solution above the max allowable weight...if I
        //picked new items in a smarter way maybe it would happen faster.
        
        //we check that totalItemsWeight > maxAllowableWeight so that we are 
        //sure that it is eventually possible to get higher than maxAllowableWeight
        //by adding random items.
        while ((solutionWeight < maxAllowableWeight) && (totalItemsWeight > maxAllowableWeight)) 
        {
            unsigned int pos = MyRandom::randInt(0, itemCount - 1);
            solution->setBit(pos, true);
            solutionWeight = calcSolutionWeight(solution);
        }
        
        solution->setFitness(evaluateFitness(solution));
        solutions.push_back(solution);
    }
}

/////////////////////////

Crossover* KnapsackEnvironment<PermutationEncodedSolution>::configureCrossover(KnapsackEnvironmentCrossoverType type)
{
    if (type == SINGLE_POINT_CROSSOVER) {
        return new SinglePointCrossover<PermutationEncodedSolution>();
    }
    else if (type == TWO_POINT_CROSSOVER) {
        return new TwoPointCrossover<PermutationEncodedSolution>();
    }
    else if (type == UNIFORM_CROSSOVER) {
        //We don't currently support uniform crossovers on permutation types
        throw Unsupported_crossover_exception();
    }
    else {
        throw Unsupported_crossover_exception();
    }
}

Crossover* KnapsackEnvironment<PermutationEncodedSolution>::configureCrossover() 
{
    cout << "What type of crossover do you want to use?" << endl;
    cout << SINGLE_POINT_CROSSOVER << ") Single Point Crossover" << endl;
    cout << TWO_POINT_CROSSOVER << ") Two Point Crossover" << endl;
    //we don't currently support permutation encoded uniform crossovers.
    cout << '?';
    int answer;
    
    while (true) {
        cin >> answer;
        
        if (answer == SINGLE_POINT_CROSSOVER) {
            return configureCrossover(SINGLE_POINT_CROSSOVER);
        }
        else if (answer == TWO_POINT_CROSSOVER) {
            return configureCrossover(TWO_POINT_CROSSOVER);
        }
        //we don't currently support permutation encoded uniform crossovers.
        else {
            cout << "Invalid selection.  Please try again." << endl;
            cout << '?';
        }
    }
}

double KnapsackEnvironment<PermutationEncodedSolution>::calcSolutionValue(PermutationEncodedSolution* solution) 
{
    double totalWeightWithNext = 0.0;
    double totalValue = 0.0;
    unsigned int x = 0;
    
    //repeat the loop until we've run out of items
    while ( x < solution->size() ) 
    {
        //see what the weight would be if we added another item
        totalWeightWithNext += items[(*solution)[x]].weight;
        
        //if the new weight is still under the allowable range, then add the item
        if (totalWeightWithNext <= maxAllowableWeight) 
        {
            totalValue += items[(*solution)[x]].value;
        }
        //otherwise, stop adding items
        else 
        {
            break;
        } 
        
        x++;
    }
    
    return totalValue;
}

double KnapsackEnvironment<PermutationEncodedSolution>::calcSolutionWeight(PermutationEncodedSolution* solution) 
{
    double totalWeightWithNext = 0.0;
    double totalWeight = 0.0;
    unsigned int x = 0;
    
    //repeat the loop until we've run out of items
    while (x < solution->size()) 
    {
        //see what the weight would be if we added another item
        totalWeightWithNext += items[(*solution)[x]].weight;
        
        //if the new weight is still under the allowable range, then add the item
        if (totalWeightWithNext <= maxAllowableWeight) {
            totalWeight += items[(*solution)[x]].weight;
        }
        //otherwise, stop adding items
        else 
        {
            break;
        } 
        
        x++;
    }
    
    return totalWeight;
}

string KnapsackEnvironment<PermutationEncodedSolution>::formatSolution(Solution* solution) 
{
    PermutationEncodedSolution* pesSolution = (PermutationEncodedSolution*) solution;
    ostringstream returnMe;
    string separator = "";
    
    returnMe << "Items: ";
    
    //TODO: for efficiency reasons, i pulled the logic out of calculate
    //weight and calculate value.  is there a cleaner way to do this
    //such that we don't duplicate code?
    double totalWeightWithNext = 0.0;
    double solutionWeight = 0.0;
    double solutionValue = 0.0;
    unsigned int x = 0;
    
    //repeat the loop until we've run out of items
    while (x < pesSolution->size()) 
    {
        //see what the weight would be if we added another item
        totalWeightWithNext += items[(*pesSolution)[x]].weight;
        
        //if the new weight is still under the allowable range, then add the item
        if (totalWeightWithNext <= maxAllowableWeight) 
        {
            solutionWeight += items[(*pesSolution)[x]].weight;
            solutionValue += items[(*pesSolution)[x]].value;
            returnMe << separator << (*pesSolution)[x];
            separator = ", ";
        }
        //otherwise, stop adding items
        else 
            break;
        
        x++;
    }
    
    returnMe << " (weight: " << solutionWeight << ", value: " << solutionValue << ")";
                 
    return returnMe.str();
}

double KnapsackEnvironment<PermutationEncodedSolution>::evaluateFitness(Solution* solution) 
{
    PermutationEncodedSolution* besSolution = (PermutationEncodedSolution*) solution;
    
    double solutionValue = calcSolutionValue(besSolution);
    double baseValue = 100;
    
    solutionValue += baseValue;
    
    return solutionValue;
}

void KnapsackEnvironment<PermutationEncodedSolution>::generateRandomPopulation(vector<Solution*> &solutions, unsigned int size) 
{
    int itemCount = items.size();
    
    for (unsigned int x = 0; x < size; ++x) {
        PermutationEncodedSolution* solution = new PermutationEncodedSolution();
        solution->fillWithRandomPermutation(0, itemCount - 1);
        solution->setFitness(evaluateFitness(solution));
        solutions.push_back(solution);
    }
}

template <typename T>
void KnapsackEnvironment<T>::populateItemsRandomly() 
{
    int numItems;
    double minWeight;
    double maxWeight;
    double minValue;
    double maxValue;
    
    cout << "How many items do you wish to generate?" << endl;
    cin >> numItems;
    
    cout << "What do you want the lightest possible item to weigh?" << endl;
    cin >> minWeight;
    
    cout << "What do you want the heaviest possible item to weigh?" << endl;
    cin >> maxWeight;
    
    cout << "What do you want the least valuable item to be worth?" << endl;
    cin >> minValue;
    
    cout << "What do you want the most valuable item to be worth?" << endl;
    cin >> maxValue;
    
    boundValues(numItems, minWeight, maxWeight, minValue, maxValue);
    
    double weightRange = maxWeight - minWeight;
    double valueRange = maxValue - minValue;
    
    cout << "ITEM #\tVALUE\tWEIGHT" << endl;
    cout << "------\t-----\t------" << endl;
    
    for (int x = 1; x <= numItems; ++x) 
    {
        Item item;
        item.weight = (MyRandom::randDbl() * weightRange) + minWeight;
        item.value = (MyRandom::randDbl() * valueRange) + minValue;
        items.push_back(item);
        
        totalItemsWeight += item.weight;
        
        cout << x << "\t" << item.value << "\t" << item.weight << endl;
    }
}

template <typename T>
void KnapsackEnvironment<T>::populateItemsFromFile(string filename)
{
    ifstream fileStream;
    fileStream.open(filename.c_str());
    
    if (!fileStream.good())
        throw Invalid_file_exception();
    
    cout << "ITEM #\tVALUE\tWEIGHT" << endl;
    cout << "------\t-----\t------" << endl;
    
    // Read in the data from the file into the items table
    while(!fileStream.eof())
    {
        //
        Item item;
        fileStream >> item.weight;
        fileStream >> item.value;
        
        items.push_back(item);
        totalItemsWeight += item.weight;
            
        cout << item.value << "\t" << item.weight << endl;
    }
    
    fileStream.close();
}

template <typename T>
void KnapsackEnvironment<T>::boundValues(int &numItems, 
                                         double &minWeight, double &maxWeight, 
                                         double &minValue, double &maxValue) 
{
    //assure that all of our values are above zero
    numItems = max(numItems, 0);
    minWeight = max(minWeight, 0.0);
    minValue = max(minValue, 0.0);
    maxWeight = max(maxWeight, 0.0);
    maxValue = max(maxValue, 0.0);
    
    //make sure that our max values are higher than our minimum values
    double tmpWeight = maxWeight;
    maxWeight = max(maxWeight, minWeight);
    minWeight = min(minWeight, tmpWeight);
    
    double tmpValue = maxValue;
    maxValue = max(maxValue, minValue);
    minValue = min(minValue, tmpValue);
}

template <typename T>
string KnapsackEnvironment<T>::getName()
{
    return "Knapsack";
}

template class KnapsackEnvironment<PermutationEncodedSolution>;
template class KnapsackEnvironment<BitEncodedSolution>;
